package stripsPlanner;

import java.util.ArrayList;
import java.util.List;

import exceptions.NoOperatorException;
import exceptions.RevertException;

import stripsLanguage.Binding;
import stripsLanguage.Instance;
import stripsLanguage.Link;
import stripsLanguage.Operator;
import stripsLanguage.Ordering;
import stripsLanguage.Plan;
import stripsLanguage.Predicate;
import stripsLanguage.Step;

public class ChooseOperator extends BuildLinearizations{

	public ChooseOperator() {}
	
	public static final int StepInterval = 1000; 	
	
	/***
	 * znalezc krok, badz operator
	 * @param plan
	 * @param operator
	 * @param subgoal
	 * @throws NoOperatorException 
	 * TEST niepotrzebny
	 */
	public StoredChoosing chooseOperator(
		Plan plan,
		List<Operator> operators,
		Subgoal subgoal,
		StoredChoosing storedChoosing)
		throws RevertException
	{
		// trzyma w sobie liczbe nieznanych instancji w predykacie do spelnienia
		//int unknowns = countUnknowns(subgoal.getPredicateOfSubgoal().getArguments());
		
		StepAndBinding stepAndBinding = chooseStep(plan, operators, subgoal,storedChoosing);
		
		/**if(!isThereSenseInIt(
			stepAndBinding.getStepToBeAdded().getId(),
			plan.getSteps().size(),
			unknowns,
			subgoal.getPredicateOfSubgoal().getArguments().size()))
				throw new RevertException("chooseOperator: dodanie tego operatora nic nie zmieni");
		**/
		
		// add a casual link
		plan.getLinks().add(new Link(
			stepAndBinding.getStepToBeAdded(),
			subgoal.getStepOfSubgoal(),
			subgoal.getPredicateOfSubgoal()));
		
		// add the ordering if its not Start
		if(stepAndBinding.getStepToBeAdded().getId() != 1)
			plan.getOrderings().add(
				new Ordering(
					stepAndBinding.getStepToBeAdded(),
					subgoal.getStepOfSubgoal()));
		
		// add the binding
		plan.getBindings().add(
			stepAndBinding.getNewBinding());
		
		// if its a new step, add orderings
		if(stepAndBinding.getStepToBeAdded().getId() > plan.getSteps().size())
		{
			plan.getSteps().add(stepAndBinding.getStepToBeAdded());
			
			// add only if 2nd is not Finish
			if(subgoal.getStepOfSubgoal().getId() != 2)
				plan.getOrderings().add(new Ordering(
					stepAndBinding.getStepToBeAdded(),
					plan.getSteps().get(1))); // step -> Finish
			plan.getOrderings().add(new Ordering(
				plan.getSteps().get(0), // Start -> step
				stepAndBinding.getStepToBeAdded()));
		}
		return stepAndBinding.getStored();
	}
	
	/**
	 * funkcja tworzy z operatora krok, jesli predykat tego operatora nadaje sie do wykorzystania
	 * @param operator
	 * @param pred
	 * @param aStep
	 * @param subgoal
	 * @param plan
	 * @param step
	 * @return
	 * TEST niepotrzeny
	 */
	private StepAndBinding checkEffector(
		Operator operator,
		int pred,
		boolean aStep,
		Subgoal subgoal,
		Plan plan,
		Step step)
	{
		long t;
		
		//System.out.println("Bada predykat: " + operator.getEffects().get(pred).getName());
		if(makeDecisionAboutEffector( // decyzja, czy wykonac cos z tym predykatem
			operator.getEffects().get(pred).getNegation(),
			operator.getEffects().get(pred).getNrOfType(),
			subgoal.getPredicateOfSubgoal().getNrOfType()) != 3)
			return null;

		if(!aStep)// have to add a new step
		{
			step = new Step(
				plan.getSteps().size() + 1, // id
				operator.getTypeOfOperator(),
				operator.getName(),
				operator.copyPreconditions(),
				operator.copyEffects());
			step.unifyInstances();					
			for(Predicate pre: step.getPreconditions())
				pre.setIdOfPredicate(plan.getMaxIdOfPredicate());
			for(Predicate pre: step.getEffects())
				pre.setIdOfPredicate(plan.getMaxIdOfPredicate());
		} 
				
		Predicate effectOfNewPredicate = step.getEffects().get(pred);
		if(step.getId() == 1) // start & operator
		{						
			//System.out.println("start & operator");
			if(differentInstances(
				subgoal.getPredicateOfSubgoal().getArguments(),
				effectOfNewPredicate.getArguments()))				
					return null; // nalezy szukac innego predykatu			
			if(aStep && secondTimeThisInstance(
				subgoal.getStepOfSubgoal().getId(),
				plan.getSteps(),
				plan.getBindings(),
				createCheckList(
					plan.getSteps().size(),
					step.getId()),		
				subgoal.getPredicateOfSubgoal(),
				effectOfNewPredicate))
					return null; // nalezy szukac innego predykatu
			setInstances(
				subgoal.getStepOfSubgoal(),
				subgoal.getPredicateOfSubgoal().getArguments(),
				effectOfNewPredicate.getArguments());
			
			
			t = System.currentTimeMillis();
			unifyVariables(
				plan.getSteps(),
				plan.getBindings(),
				subgoal.getStepOfSubgoal().getId());
			MyTimeClass.getInstance().addUnifyVariables(System.currentTimeMillis()-t);	
			
			
		} else if(subgoal.getStepOfSubgoal().getId() == 2) // operator && finish
		{
			//System.out.println("operator & finish");				
			if(differentInstances(
				subgoal.getPredicateOfSubgoal().getArguments(),
				effectOfNewPredicate.getArguments()))				
					return null; // nalezy szukac innego predykatu						
			if(aStep && secondTimeThisInstance(
				step.getId(),
				plan.getSteps(),
				plan.getBindings(),
				createCheckList(
					plan.getSteps().size(),
					subgoal.getStepOfSubgoal().getId()),
				effectOfNewPredicate,
				subgoal.getPredicateOfSubgoal()))
					return null; // nalezy szukac innego predykatu	
			setInstances(
				step,
				effectOfNewPredicate.getArguments(),
				subgoal.getPredicateOfSubgoal().getArguments());
			
			
			t = System.currentTimeMillis();
			unifyVariables(
				plan.getSteps(),
				plan.getBindings(),
				step.getId());
			MyTimeClass.getInstance().addUnifyVariables(System.currentTimeMillis()-t);				
			
		} else // operator & operator
		{
			//System.out.println("operator & operator");
			if(differentInstances(
				subgoal.getPredicateOfSubgoal().getArguments(),
				effectOfNewPredicate.getArguments()))				
					return null; // nalezy szukac innego predykatu
			if(aStep && secondTimeThisInstance(
				step.getId(),
				plan.getSteps(),
				plan.getBindings(),
				createCheckList(
					plan.getSteps().size(),
					subgoal.getStepOfSubgoal().getId()),				
				effectOfNewPredicate,
				subgoal.getPredicateOfSubgoal()))
					return null; // nalezy szukac innego predykatu					
			if(aStep && secondTimeThisInstance(
				subgoal.getStepOfSubgoal().getId(),
				plan.getSteps(),
				plan.getBindings(),				
				createCheckList(
					plan.getSteps().size(),
					step.getId()),
				subgoal.getPredicateOfSubgoal(),
				effectOfNewPredicate))
					return null; // nalezy szukac innego predykatu	
			// przepisac, bo dobre wartosci, chyba ze w precondition brak wartosci			
			for(int i = 0; i < subgoal.getPredicateOfSubgoal().getArguments().size(); i++)
			{
				Instance insSubgoal = subgoal.getPredicateOfSubgoal().getArguments().get(i);
				Instance insStep = effectOfNewPredicate.getArguments().get(i);
				if((insSubgoal.getId() < 0)
					&& (insStep.getId() < 0))						
					; // tu wartosc sie pojawi przez unifyVariables w przyszlosci
				else if(insSubgoal.getId() < 0)
				{
					subgoal.getStepOfSubgoal().setInstance(
						insSubgoal.getId(),
						insStep);
					
					t = System.currentTimeMillis();
					unifyVariables(
						plan.getSteps(),
						plan.getBindings(),
						subgoal.getStepOfSubgoal().getId());
					MyTimeClass.getInstance().addUnifyVariables(System.currentTimeMillis()-t);	
					
				} else if(insStep.getId() < 0)
				{
					step.setInstance(
						insStep.getId(),
						insSubgoal);
					
					t = System.currentTimeMillis();
					unifyVariables(
						plan.getSteps(),
						plan.getBindings(),
						step.getId());
					MyTimeClass.getInstance().addUnifyVariables(System.currentTimeMillis()-t);	
					
				}
			}
		}			
		return new StepAndBinding(
			step,
			new Binding(
				effectOfNewPredicate,
				subgoal.getPredicateOfSubgoal(),
				step.getId(),// id of effect
				subgoal.getStepOfSubgoal().getId()),
			null);
	}
	
	/**
	 * decyduje, czy bedzie jeden z krokow wykonany, czy powstanie nowy krok z operatora
	 * steruje przegladaniem predykatow, aby znalezc ten mogacy spelnic subgoal
	 * @param plan
	 * @param operators
	 * @param subgoal
	 * @param storedChoosing
	 * @return
	 * @throws NoOperatorException
	 * TEST niepotrzebny
	 */
	private StepAndBinding chooseStep(
		Plan plan,
		List<Operator> operators,
		Subgoal subgoal,
		StoredChoosing storedChoosing)
		throws RevertException
	{	
		List<Integer> listOfPicksIds = null;
		int indexInList = -1;
		int firstPredicateToCheckInOperator = 0;
	
		if(storedChoosing == null) // this step is searched for the first time		
			listOfPicksIds = createListOfPicksIds(plan.getSteps(), operators);
		else {
			listOfPicksIds = storedChoosing.getListOfPicksIds();
			indexInList = storedChoosing.getIndexInList();
			firstPredicateToCheckInOperator = storedChoosing.getNrOfPredicateInSubgoalProver() + 1;
		}
	
		List<List<Integer>> matrix = buildLinearization(
			plan.getSteps(),
			plan.getOrderings());//do make decision
		
		boolean first = true;
		while(listOfPicksIds.size() > 0 )  // has value = true, when a new operator is found
		{
			// 	bierze po kolei od pierwszego
			if(!first || indexInList < 0)
				indexInList = 0;
			first = false;
		
			Operator operator = null;
			Step step = null;
			boolean aStep = false;		
			if(listOfPicksIds.get(indexInList) >= StepInterval) // it is a step
			{
				step = plan.getSteps().get((listOfPicksIds.get(indexInList) - StepInterval) - 1);
				//System.out.println("*** Krok: " + step.getName() + ", id: " + step.getId());
				aStep = true;
				operator = step;	
			} else { // it is an operator
				operator = operators.get( listOfPicksIds.get(indexInList) - 1 );
				//System.out.println("*** Operator: " + operator.getName());				
			}
	
			int stepId = -1; // potrzebne w makeDecision
			if(aStep) stepId = step.getId();
			int decision = makeDecisionAboutStep(
				aStep,
				stepId,
				subgoal.getStepOfSubgoal().getId(),
				matrix);
			if(decision == 4)
			{					
				for(int p = firstPredicateToCheckInOperator; p < operator.getEffects().size(); p++)		
				{
					StepAndBinding sb = checkEffector(
						operator,
						p,				
						aStep,
						subgoal,
						plan,
						step);
					if(sb == null)
						continue;
					sb.setStored(
						new StoredChoosing(
							listOfPicksIds,
							p,
							indexInList));
					return sb;
				}
			}
			firstPredicateToCheckInOperator = 0;			
			//System.out.println("Sprobuj z innym operatorem, uzasadnienie: " + decision);
			listOfPicksIds.remove(indexInList);			
		}	
		throw(new RevertException("Nie znaleziono rozwiazania dla tego problemu"));
	}

	/**
	 * @param plan
	 * @return
	 */
	//TEST
	public List<List<Integer>> buildLinearization(
		List<Step> steps,
		List<Ordering> orderings) 
	{
		long t = System.currentTimeMillis();		
		// stworz wszystkie mozliwe linearyzacje
		List<List<Integer>> matrix = new ArrayList<List<Integer>>();
		List<Integer> firstList = new ArrayList<Integer>();

		List<Ordering> ors = new ArrayList<Ordering>();
		for(Ordering o: orderings)
			if(o.getFirstStep().getId() != 1
				&& o.getLastStep().getId() != 2)
				ors.add(o);
		firstList.add(1);
		Step first = steps.remove(0);// start i finish wiem, jak sa umieszczony w macierzy
		Step last = steps.remove(0);
		new BuildLinearizations().buildLinearizations(			
			matrix,
			firstList,
			steps,
			ors);
		steps.add(0,last);
		steps.add(0,first);
		
		MyTimeClass.getInstance().addBuildLinearization(System.currentTimeMillis()-t);	
		return matrix;
	}	
	
	//TEST
	protected int countUnknowns(List<Instance> list)
	{
		long t = System.currentTimeMillis();
		int unknowns = 0;
		for(Instance i: list)
			if(i.getId() < 0)
				unknowns++;
		MyTimeClass.getInstance().addCountUnknowns(System.currentTimeMillis()-t);
		return unknowns;
	}

	
	//TEST
	protected List<Boolean> createCheckList(int nrOfSteps,int idOfInfluenceStep)
	{
		long t = System.currentTimeMillis();
		List<Boolean> c = new ArrayList<Boolean>();
		for(int i = 0; i < nrOfSteps; i++)
			if((i+1) == idOfInfluenceStep)
				c.add(true);
			else
				c.add(false);
		MyTimeClass.getInstance().addCreateCheckList(System.currentTimeMillis()-t);
		return c;
	}
	
	//TEST
	protected List<Integer> createListOfPicksIds(List<Step> steps, List<Operator> operators)
	{
		// musi byc lista, by kolejno sprawdzac, a potem usunac...
		List<Integer> listOfPicksIds = new ArrayList<Integer>();
		listOfPicksIds.add(1 + StepInterval); // dodaje Start...
		for(int i = 3; i <= steps.size(); i++) // ... i pozostale
			listOfPicksIds.add(i + StepInterval);
		for(int i = 1; i <= operators.size() - 2; i++) // dodaje operatory bez dwoch ostatnich:Start&Finish
			listOfPicksIds.add(i);
		return listOfPicksIds;
	}
	
	/**
	 * 
	 * @return true, jesli wykryje rozne przypisania w dwoch predykatach
	 * TEST
	 */
	protected boolean differentInstances(
		List<Instance> list1,
		List<Instance> list2)
	{
		for(int i = 0; i < list1.size();i++)
			if(		(list1.get(i).getId() > 0) // zdefiniowana
				&&  (list2.get(i).getId() > 0) //zdefiniowana
				&&  (list1.get(i).getId() != list2.get(i).getId())) // i rozne			
					return true; // rozne przypisania
		return false;
	}		
			
	/**
	 * zwraca true, jesli w kazdej linearyzacji first jest za second
	 * @param matrix
	 * @param idOfFirstStep
	 * @param idOfSecondStep
	 * @return
	 * TEST
	 */
	public boolean isAlwaysAfter(List<List<Integer>> matrix, int idOfFirstStep, int idOfSecondStep)
	{	
		long t = System.currentTimeMillis(); 
		for(List<Integer> list: matrix)				
			for(int i = 0; i < list.size(); ++i)
				if(list.get(i) == idOfFirstStep)
				{
					MyTimeClass.getInstance().addIsAlwaysAfter(System.currentTimeMillis()-t);
					return false;
				}
				else if(list.get(i) == idOfSecondStep)
					break;
		MyTimeClass.getInstance().addIsAlwaysAfter(System.currentTimeMillis()-t);
		return true;
	}
	
	/**
	 * zwraca true, jesli w jakiejs linearyzacji first jest za second
	 * @param matrix
	 * @param idOfFirstStep
	 * @param idOfSecondStep
	 * @return
	 * TEST
	 */
	public boolean isOnceAfter(List<List<Integer>> matrix, int idOfFirstStep, int idOfSecondStep)
	{
		long t = System.currentTimeMillis(); 
		for(List<Integer> list: matrix)
			for(int i = 0; i < list.size(); ++i)
				if(list.get(i) == idOfFirstStep)
					break;
				else if(list.get(i) == idOfSecondStep)
				{
					MyTimeClass.getInstance().addIsOnceAfter(System.currentTimeMillis()-t);				
					return true;
				}
		MyTimeClass.getInstance().addIsOnceAfter(System.currentTimeMillis()-t);
		return false;
	}
	
	/**
	 * 
	 * @param nrOfStepsNow
	 * @param nrOfStepsBefore
	 * @return
	 * 		= false, jesli wprowadzone zmiany nie prowadza do rezultatu:
	 * 			- jesli stworzyl krok, a subgoal mial wszystko puste
	 * TEST 
	 */
	protected boolean isThereSenseInIt(
		int nrOfStepsNow,
		int nrOfStepsBefore,
		int unknowns,
		int nrOfInstancesInPrecondition)
	{
		if(nrOfStepsNow > nrOfStepsBefore // powstal nowy krok
			&& unknowns	== nrOfInstancesInPrecondition) // ktory ma same niewiadome
			return false;
		return true;
	}
	
	/**
	 * 
	 * @return
	 * 		= 1, predykat jest zaprzeczajacy, nie nalezy go wykorzystywac
	 * 		= 2, predykat nie zostanie wykorzystany, bo jest zlego typu
	 * 		= 3, predykat moze zostanie wykorzystany
	 * TEST niepotrzebny
	 */
	protected int makeDecisionAboutEffector(
		boolean negationOfOperatorEffector,
		int nrOfTypeOfEffector,
		int nrOfTypeOfSubgoalPrecondition)
	{
		//System.out.println("Bada predykat: " + operator.getEffects().get(pred).getName());	
		if (!negationOfOperatorEffector)	// predykat zaprzeczajacy nam nie spelni warunku
			return 1;
		if(nrOfTypeOfEffector != nrOfTypeOfSubgoalPrecondition)
			return 2; // rozne typy predykatow - bez uzycia
		else
			return 3;
	}
	
	/**
	 * 
	 * @param aStep
	 * @param idOfStep
	 * @param idOfSubgoalStep
	 * @return
	 * 		= 1, krok wskazuje na samego siebie
	 * 		= 2, kroki sa polaczone przez Ordering tak, ze nie da sie ich w druga strone polaczyc
	 * 		= 3, proba polaczenia Start i Finish - bez sensu
	 * 		= 4, mozna probowac
	 * TEST
	 */
	protected int makeDecisionAboutStep(
		boolean aStep,
		int idOfStep,
		int idOfSubgoalStep,
		List<List<Integer>> matrix)
	{
		if(		aStep &&
				idOfStep == idOfSubgoalStep)
			return 1;
		else if(aStep &&
				idOfStep == 1 &&
				idOfSubgoalStep == 2) // start & finish			
			return 2;
		else if(aStep &&
				isAlwaysAfter(
					matrix, // matrix
					idOfStep,
					idOfSubgoalStep))
			return 3;
		else
			return 4;
	}
	
	/**
	 * w kazdym operatorze zmienne musza byc powiazane z roznymi instancjami
	 * @return true, jesli nowa instancja juz jest w kroku
	 * TEST
	 */
	protected boolean secondTimeThisInstance(
		int idOfStep,
		List<Step> steps,
		List<Binding> bindings,
		List<Boolean> checked,	
		Predicate predicateToBeFilled,
		Predicate predicateThatFilles)
	{
		for(int i = 0; i < predicateToBeFilled.getArguments().size();i++)
			if(predicateToBeFilled.getArguments().get(i).getId() < 0
				&& predicateThatFilles.getArguments().get(i).getId() > 0)
				for(Instance instance: steps.get(idOfStep-1).getInstances())
					if(instance.getId() == predicateThatFilles.getInstance(i))					
						//System.out.println("Chce dodac drugi raz te sama instancje");
						return true;
		checked.set(idOfStep-1,true);
		
		for(Binding b: bindings)		
			if(b.getIdOfPreconditionPredicateStep() == idOfStep
				&& !checked.get(b.getIdOfEffectPredicateStep()-1))		
			{
				// stworz nowy, sztuczny predykat
				Predicate p = b.getPreconditionPredicate().copyPredicate();
				boolean jest = false;
				// sprawdza czy nowa instancja jest w tym predykacie
				for(int i = 0; i < predicateToBeFilled.getArguments().size();i++)
					if(predicateToBeFilled.getArguments().get(i).getId() < 0
						&& predicateThatFilles.getArguments().get(i).getId() > 0)
						for(int j = 0; j < b.getPreconditionPredicate().getArguments().size(); ++j)
							if(p.getArguments().get(j).getId()
								== predicateToBeFilled.getArguments().get(i).getId())
							{
								jest = true;
								p.setInstance(j,predicateThatFilles.getArguments().get(i));
							}
				if(!jest) //nic nie zmienilo
					continue;

				if(secondTimeThisInstance(
					b.getIdOfEffectPredicateStep(),
					steps,
					bindings,
					checked,				
					b.getEffectPredicate(),
					p))				
						return true;
			} else if (b.getIdOfEffectPredicateStep() == idOfStep
				&& !checked.get(b.getIdOfPreconditionPredicateStep()-1))
			{
				// stworz nowy, sztuczny predykat
				Predicate p = b.getEffectPredicate().copyPredicate();				
				boolean jest = false;
				// sprawdza czy nowa instancja jest w tym predykacie
				for(int i = 0; i < predicateToBeFilled.getArguments().size();i++)
					if(predicateToBeFilled.getArguments().get(i).getId() < 0
						&& predicateThatFilles.getArguments().get(i).getId() > 0)
						for(int j = 0; j < b.getEffectPredicate().getArguments().size(); ++j)
							if(p.getArguments().get(j).getId()
								== predicateToBeFilled.getArguments().get(i).getId())
							{
								jest = true;
								p.setInstance(j,predicateThatFilles.getArguments().get(i));
							}
				if(!jest) //nic nie zmienilo
					continue;

				if(secondTimeThisInstance(
					b.getIdOfPreconditionPredicateStep(),
					steps,
					bindings,
					checked,				
					b.getPreconditionPredicate(),
					p))				
						return true;				
			}
		return false;
	}
	
	/**
	 * przepisuje instancje z listy do listy, jesli id jest ujemne
	 * TEST
	 */
	protected void setInstances(
		Step stepToBeFilled,
		List<Instance> listToBeFilled,
		List<Instance> listThatFills)
	{
		for(int i = 0; i < listToBeFilled.size(); i++) // jak nie przypisana, to przypisz
			if(listToBeFilled.get(i).getId() < 0)
				stepToBeFilled.setInstance(
					listToBeFilled.get(i).getId(),
					listThatFills.get(i));
	}
	
	// podstawia pod zmienne instancje tam, gdzie pojawila sie nowa instancja
	// i sa polaczenia poprzez linki
	// TEST
	protected void unifyVariables(List<Step> steps, List<Binding> bindings, int idOfStep)
	{
		//System.out.println("\t\t\t*** W unifyvariables");
		for(Binding b: bindings)		
			if(b.getIdOfPreconditionPredicateStep() == idOfStep)
			{
				Step boundStep = steps.get(b.getIdOfEffectPredicateStep()-1);				
				for(int i = 0; i < b.getPreconditionPredicate().getArguments().size(); ++i)						
					if(b.getPreconditionPredicate().getArguments().get(i).getId() > 0
						&& b.getEffectPredicate().getArguments().get(i).getId() < 0)
					{
						boundStep.setInstance(
							b.getEffectPredicate().getArguments().get(i).getId(),
							b.getPreconditionPredicate().getArguments().get(i));
						unifyVariables(steps,bindings, b.getIdOfEffectPredicateStep());
					}
			}
			else if (b.getIdOfEffectPredicateStep() == idOfStep)
			{
				Step boundStep = steps.get(b.getIdOfPreconditionPredicateStep()-1);				
				for(int ii = 0; ii < b.getPreconditionPredicate().getArguments().size(); ++ii)						
					if(b.getPreconditionPredicate().getArguments().get(ii).getId() < 0
						&& b.getEffectPredicate().getArguments().get(ii).getId() > 0)
					{
						boundStep.setInstance(
							b.getPreconditionPredicate().getArguments().get(ii).getId(),
							b.getEffectPredicate().getArguments().get(ii));
						unifyVariables(steps,bindings,b.getIdOfPreconditionPredicateStep());
					}
			}	
	}	
}
